在開發過程中,多少會遇到異步的 function,return 的結果是一個 callback function 或是Promise的物件,所以在調用 async function 時會有 時間差
的問題。
那時間差的測試案例如何撰寫呢?
先寫一個 Promise 的 timeout 來模擬時間差情境
ng g s share/data
getDetails()
的functionline 3
)的Promise物件 resultPromise
export class DataService {
getDetails() {
const resultPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data');
}, 1500);
});
return resultPromise;
}
}
line 21
,使用getDetails()
若是時間到了,成功會回傳data,再用全域的data去接回傳值。@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
providers: [UserService, DataService] // 增加DataService
})
export class UserComponent implements OnInit {
user: {name: string};
isLoggedIn = false;
// 增加新變數
data: string;
constructor(private userService: UserService,
private dataService: DataService) { }
// ↑ 增加DataService
ngOnInit() {
this.user = this.userService.user;
// 增加調用dataService, 取得 data
this.dataService.getDetails().then((data: string) => this.data = data);
}
}
前面已經把 Service 和 Component 都調整好,再接著處理測試案例,這次一樣寫正面和反面情境
line 3 ~ line 5
: 前一篇有提到過,還不確定的小夥伴,可以回前一天看(但簡而言之就是初始化元件,和注入service)line 8
(重點): 使用 spyOn 方法,listen dataService 裡的 getDetails
,function名字要和service裡面的方法同名,才能被監聽到,此外(and)會有回傳值,裡面會resolve一個字串的Promiseline 9
: 避免拿到undefined的情況,所以監聽狀態(detectChanges()
)line 12
: 預期拿到的資料是 undefined , 因為還沒有用到 async
的方法,所以如果toBe('Data')會是fail的it('shouldn\'t fetch data successfully if not called asynchronously', () => {
// Arrange
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
// Act 是重點!
let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
// Assert
expect(app.data).toBe(undefined);
});
正面情境是有使用 async()
的方法,可以處理異步的 function,最後在 line 15
使用whenStable
等待結果,拿到回傳值後再處理預期結果,也就是「回傳結果要是'Data'」
// ↓加這個
it('should fetch data successfully if called asynchronously', async(() => {
// Arrange
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
// Act
let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
// Assert
// ↓等待
fixture.whenStable().then(() => {
expect(app.data).toBe('Data');
});
}));
有沒有不是 Promise 寫法的異步 function,其實開發上也不少,所以Angular提供fakeAsync
方法,不用等待也能測試異步函式,就是用 tick()
,若要等待也可在 tick 裡寫毫秒,如此一來可以讓測試快速跑完。
// ↓ 設定假的Async
it('should fetch data successfully if called asynchronously', fakeAsync(() => {
let fixture = TestBed.createComponent(UserComponent);
let app = fixture.debugElement.componentInstance;
let dataService = fixture.debugElement.injector.get(DataService);
let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data'));
fixture.detectChanges();
// ↓ 多拉A夢時光機來了,一秒也不想等了
tick();
expect(app.data).toBe('Data');
}));
在瀏覽器的世界,Javascript 是單線程的溝通語言,而 browser 是多線程的 run time 環境,所以同步異步的函式一定會遇到,瞭解 async 和 fakeAsync 測試方法後,相信日後能撰寫更多不同的測試情境 Let us wait and see
下一篇來學習,「Isolated vs Non-Isolated」
參考資料來源: